package com.kantboot.functional.im.service.impl;

import cn.hutool.core.util.IdUtil;
import com.kantboot.functional.im.domain.entity.FunctionalImWebsocket;
import com.kantboot.functional.im.dto.FunctionalImPushMessageDTO;
import com.kantboot.functional.im.dto.FunctionalImWebsocketSearchDTO;
import com.kantboot.functional.im.event.WebsocketPushEvent;
import com.kantboot.functional.im.repository.FunctionalImWebSocketRepository;
import com.kantboot.functional.im.service.IFunctionalImWebSocketService;
import com.kantboot.system.user.service.ISysUserOnlineService;
import com.kantboot.system.user.service.IUserSelfService;
import jakarta.annotation.Resource;
import jakarta.websocket.Session;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 关于websocket的服务实现接口
 */
@Slf4j
@Service
public class FunctionalImWebSocketServiceImpl implements IFunctionalImWebSocketService {

    @Resource
    private FunctionalImWebSocketRepository repository;

    @Resource
    private IUserSelfService userSelfService;

    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private ISysUserOnlineService userOnlineService;


    /**
     * 存放在线的客户端
     */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    @Override
    public String generateCid() {
        // 获取当前用户ID
        Long selfId = userSelfService.getSelfId();
        // 生成cid
        String cid = IdUtil.simpleUUID()+IdUtil.simpleUUID() + "-" + (new Date().getTime());

        FunctionalImWebsocket functionalWebSocket =
                new FunctionalImWebsocket()
                        .setCid(cid)
                        .setUserId(selfId)
                        .setHeartBeatCount(0L)
                        .setIsOpened(false)
                        .setIsClosed(false);

        // 保存到数据库
        repository.save(functionalWebSocket);
        return cid;
    }

    @Override
    public Session getSession(String cid) {
        return clients.get(cid);
    }

    @Override
    public List<FunctionalImWebsocket> getAllByUserId(Long userId) {
        return repository.findByUserId(userId);
    }

    @Override
    public List<FunctionalImWebsocket> getAllOpenedByUserId(Long userId) {
        FunctionalImWebsocketSearchDTO param = new FunctionalImWebsocketSearchDTO();
        param.setUserId(userId);
        // 已打开
        param.setIsOpened(true);
        // 未关闭
        param.setIsClosed(false);

        // 当前时间-60秒,即最后更新时间在60秒内,才算是在线，不然就是离线
        long lastGmtModifiedEndTime = new Date().getTime() - 60 * 1000;

        param.setLastGmtModifiedEnd(new Date(lastGmtModifiedEndTime));

        return repository.findByDto(param);
    }

    @Override
    @SneakyThrows
    public void openWebSocket(String cid, Session session) {
        FunctionalImWebsocket bycid = repository.findByCid(cid);
        if (bycid == null) {
            log.error("cid:{}不存在",cid);
            // 发送websocketOpenFail指令，通知客户端连接失败
            sendSimpleEmit(session,"websocketOpenFail",
                    Map.of(
                            "reasonCode","cidNotExist",
                            "message","cid不存在")
            );
            // 等待3秒，让客户端尽量接收到消息
            Thread.sleep(3000);
            // 阻止新连接
            session.close();
            return;
        }

        if (bycid.getIsOpened()) {
            log.error("cid:{}已经打开",cid);
            // 发送websocketOpenFail指令，通知客户端连接失败
            sendSimpleEmit(session,"websocketOpenFail",
                    Map.of(
                            "reasonCode","cidAlreadyOpen",
                            "message","cid已经打开")
            );

            // 等待3秒，让客户端尽量接收到消息
            Thread.sleep(3000);
            // 阻止新连接
            session.close();
            return;
        }

        if (bycid.getIsClosed()) {
            log.error("cid:{}已经关闭",cid);

            // 发送websocketOpenFail指令，通知客户端连接失败
            sendSimpleEmit(session,"websocketOpenFail",
                    Map.of(
                            "reasonCode","cidAlreadyClosed",
                            "message","cid已经关闭")
            );

            // 等待3秒，让客户端尽量接收到消息
            Thread.sleep(3000);
            // 阻止新连接
            session.close();
            return;
        }

        // 查看是否有相同的cid，则阻止新连接
        Session oldSession = clients.get(cid);
        if (oldSession != null) {
            log.error("cid:{}已经存在",cid);
            // 发送websocketOpenFail指令，通知客户端连接失败
            sendSimpleEmit(session,"websocketOpenFail",
                    Map.of(
                            "reasonCode","cidAlreadyExist",
                            "message","cid已经存在")
            );

            // 等待3秒，让客户端尽量接收到消息
            Thread.sleep(3000);
            // 阻止新连接
            session.close();
            return;
        }

        // 将新连接放入clients
        clients.put(cid, session);

        // 设置为已打开
        bycid.setIsOpened(true);
        repository.save(bycid);
        // 用户上线
        userOnlineService.online(bycid.getUserId());

        // 发送websocketOpenSuccess指令，通知客户端连接成功
        sendSimpleEmit(session,"websocketOpenSuccess",null);

        log.info("有新连接加入：{}，cid:{}，用户ID:{}",
                session.getId(),
                cid,
                bycid.getUserId());
    }


    @Override
    public void closeWebSocket(String cid) {
        // 从clients中移除
        clients.remove(cid);

        // 删除数据库中的记录
        FunctionalImWebsocket bycid = repository.findByCid(cid);
        repository.delete(bycid);
        // 用户下线
        userOnlineService.offline(bycid.getUserId());

        log.info("连接关闭：cid:{}", cid);
    }

    @Override
    public void sendSimpleEmit(Session session, String emit,Object data) {
        FunctionalImPushMessageDTO message =
                new FunctionalImPushMessageDTO()
                        .setPushTypeCode(FunctionalImPushMessageDTO.PUSH_TYPE_CODE_CID)
                        .setPayload(
                                new FunctionalImPushMessageDTO.Payload()
                                        .setEmit(emit)
                                        .setData(data)
                        )
                        .setBrokers(
                                List.of(new FunctionalImPushMessageDTO.Broker()
                                        .setCode(FunctionalImPushMessageDTO.Broker.CODE_WEBSOCKET)
                                )
                        );

        WebsocketPushEvent websocketPushEvent = new WebsocketPushEvent(this,message,session);
        applicationContext.publishEvent(websocketPushEvent);
    }

    @Override
    public void onWebsocketMessage(String cid, String message) {
        if("peng".equals(message)) {
            // 心跳
            heartbeat(cid);
            return;
        }
    }

    @Override
    public void heartbeat(String cid) {
        FunctionalImWebsocket bycid = repository.findByCid(cid);
        if (bycid == null) {
            log.error("cid:{}不存在",cid);
            return;
        }

        // 心跳次数+1
        bycid.setHeartBeatCount(bycid.getHeartBeatCount()+1);
        // 设置最后一次心跳时间
        bycid.setGmtLastHeartBeat(new Date());
        repository.save(bycid);
        // 用户心跳
        userOnlineService.heartbeat(bycid.getUserId());
    }
}
